#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

/*
@2024-10-06: 增加函数
    int extractCammnder( char sock_msg[1024], char *operator, char *operand ),
    用于从socket消息中提取"操作符"和"操作数","操作符"和"操作数"之间用竖线"|"分隔.

*/

#ifndef PTHREAD_MUTEX_NORMAL
    #define PTHREAD_MUTEX_NORMAL PTHREAD_MUTEX_TIMED_NP
#endif

const int Port = 8888;

#define LISTEN_QUEUE    16

// #define MAX_CLIENT_NUM 16

typedef struct _SockInfo {
    struct sockaddr_in addr;
    int fd;
}SockInfo;

SockInfo infos[LISTEN_QUEUE];

pthread_mutex_t abort_mutex;


// static int global_socket_cnt = 0;
static int global_abort_exposure = 0;

double exp_t_s = 5;         // exposure time in s
double exp_t_ms;        // exposure time is ms = exp_t_s * 1000

int img_idx = -1;

void *serveForClient(void * arg);
void startExposure(void *arg);
void abortExposure(void *arg);


int extractCommand( const char sock_msg[1024], char *operator, char *operand );
// int clear_operator( char *op );

int main(void) {

    // pthread_mutex_init(&abort_mutex, PTHREAD_MUTEX_TIMED_NP);
    pthread_mutex_init(&abort_mutex, PTHREAD_MUTEX_NORMAL);


    // client Info
    int socket_max_size = sizeof(infos) / sizeof(infos[0]);
    int i;
    for( i=0; i<socket_max_size; i++ ){
        bzero(&infos[i], sizeof(infos[i]));
        infos[i].fd = -1;
    }

    //初始化socket
    int sock_fd = socket(AF_INET,SOCK_STREAM,0);;
    int on = 1;                                         // Enable address reuse
    setsockopt( sock_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );

    struct sockaddr_in mysock;
    struct sockaddr_in client_addr;                     // socket client
    socklen_t sin_size = sizeof(struct sockaddr_in);

    //编辑地址信息
    memset(&mysock, 0, sizeof(mysock));
    bzero( &client_addr, sizeof(client_addr) );

    mysock.sin_family       = AF_INET;
    mysock.sin_port         = htons(Port);
    mysock.sin_addr.s_addr  = htonl(INADDR_ANY);

    //绑定地址，然后监听
    if( bind(sock_fd,(struct sockaddr *)&mysock,sizeof(struct sockaddr)) == -1 ){
        printf("failed to bind socket, quit.\n");
        exit(-1);
    }

    if( listen(sock_fd, LISTEN_QUEUE) < -1) {
        printf("listen error.\n");
        exit(-1);
    }

    fprintf(stdout, "############################################\n");
    fprintf(stdout, "  Socket Server start listening ...         \n");
    fprintf(stdout, "############################################\n");
    
    while(1) {
        
        SockInfo *pinfo = NULL;

        for( i=0; i<socket_max_size; i++ ){
            if( infos[i].fd == -1 ){
                pinfo = &infos[i];
                break;
            }
        }
        
        //accpet
        // int cfd = accept(sock_fd, (struct sockaddr *)&(pinfo->addr), &sin_size);
        int cfd = accept( sock_fd, (struct sockaddr *)&client_addr, &sin_size ); //accpet
        if( -1 == cfd ) {
            perror("accept socket connection failed\n");
            continue;
        }

        pinfo->fd = cfd;

        if( pinfo != NULL ){
            fprintf(stdout, "[main thread] --> SockInfo infos[%2d] is activated\n", i);
            fprintf(stdout, "[main thread] --> socket fd: %d\n\n", cfd);
        }

        // create new thread
        pthread_t tid;
        pthread_create(&tid, NULL, serveForClient, pinfo);
        pthread_detach(tid);
    }

    close(sock_fd);
    return 0;
}

void *serveForClient(void *arg) {
    
    SockInfo *pinfo = (SockInfo*)arg;

    // char ip[32];
    // printf("Client IP: %s, port: %d\n",
    //         inet_ntop(AF_INET, &pinfo->addr.sin_addr.s_addr, ip, sizeof(ip)),
    //         ntohs(pinfo->addr.sin_port));


    int new_fd = pinfo->fd;
    // int socket_cnt = global_socket_cnt;
    char recvbuf[1024];
    char sendbuf[1024] = "recv successfully.\n";
    char return_msg[1040];

    // fprintf(stdout, "my pthered_id is: %d\n", new_fd);

    char operator[128] = "";
    char operand[256] = "";
    int operand_i = 0;
    double operand_f = 0.0;

    while(1) {

        // printf("Client IP: %s, port: %d --> ",
        //     inet_ntop(AF_INET, &pinfo->addr.sin_addr.s_addr, ip, sizeof(ip)),
        //     ntohs(pinfo->addr.sin_port));

        socklen_t sk_len = recv(new_fd, recvbuf, sizeof(recvbuf), 0);
        // fputs(recvbuf,stdout);

        sprintf(return_msg, "%s_ok", recvbuf);

        if( sk_len > 0 ){
            // fprintf(stdout, "[DEBUG] %s\n", recvbuf);

            extractCommand( recvbuf, operator, operand );
            // printf("** operator: %s\n", operator);
            // printf("** operand:  %s\n", operand);

            // if( strcmp(recvbuf, "exposure") == 0 ){
            if( strcmp(operator, "exposure") == 0 ){
                
                pthread_mutex_lock(&abort_mutex);
                global_abort_exposure = 0;
                pthread_mutex_unlock(&abort_mutex);

                // printf("> start exposure for %g seconds.\n", exp_t_s);
                startExposure(arg);

                memset(sendbuf,0,sizeof(sendbuf));
                // strcpy(sendbuf, "exposure_ok");
                strcpy(sendbuf, return_msg);
                send(new_fd, sendbuf, strlen(sendbuf), 0);
            }
            // else if( strcmp(recvbuf, "set_exp_t") == 0 ){
            else if( strcmp(operator, "set_exp_t") == 0 ){
                operand_f = atof(operand);
                exp_t_s = operand_f;
                // printf("> ############ exposure time set to: %g seconds\n", exp_t_s);
                memset(sendbuf,0,sizeof(sendbuf));

                // char tmp[256];
                // sprintf(tmp,"%s_ok", recvbuf);
                // strcpy(sendbuf, tmp);
                strcpy(sendbuf, return_msg);

                // usleep(1000);

                send(new_fd, sendbuf, strlen(sendbuf), 0);
                // printf("> ############ send %s to client\n", sendbuf);
            }
            // else if( strcmp(recvbuf, "abort") == 0 ){
            else if( strcmp(operator, "abort") == 0 ){
                printf("> recieved abort signal.\n");
                
                abortExposure(arg);

                memset(sendbuf,0,sizeof(sendbuf));
                // strcpy(sendbuf, "abort_ok");
                strcpy(sendbuf, return_msg);
                send(new_fd, sendbuf, strlen(sendbuf), 0);
                printf("> send abort_ok to client\n");
            }
            // else if( strcmp(recvbuf, "exit") == 0 ){
            else if( strcmp(operator, "exit") == 0 ){
                // char stmp[256];
                // sprintf(stmp, "close connection from socket_fd: %d\n", new_fd);
                // send(new_fd, stmp, sizeof(stmp), 0);
                send(new_fd, "connection close.\n", sizeof("connection close.\n"), 0);

                break;
            }
            // else if( strcmp(recvbuf, "check_connection") == 0 ){
            else if( strcmp(operator, "check_connection") == 0 ){
                memset(sendbuf,0,sizeof(sendbuf));
                // strcpy(sendbuf, "check_connection_ok");
                strcpy(sendbuf, return_msg);
                send(new_fd, sendbuf, strlen(sendbuf), 0);
                printf("**> sendbuf: %s\n", sendbuf);
            }
            // else if( strcmp(recvbuf, "whoareyou") == 0 ){
            else if( strcmp(operator, "whoareyou") == 0 ){
                printf("> received whoareyou cmd ...\n");
                memset(sendbuf,0,sizeof(sendbuf));
                strcpy(sendbuf, "IamSchmidt");
                // strcpy(sendbuf, return_msg);
                send(new_fd, sendbuf, strlen(sendbuf), 0);
                printf("> send IamSchmidt to Camera finder ...\n");
            // }else if( strcmp(recvbuf, "set_as_master") == 0) {
            }else if( strcmp(operator, "set_as_master") == 0) {
                printf("> received set_as master\n");
                printf(">[fake-response] ....\n");
                // sleep(1);

                // strcpy(sendbuf, "set_as_master_ok");
                strcpy(sendbuf, return_msg);
                send(new_fd, sendbuf, strlen(sendbuf), 0);
                printf("> send set_as_master_ok\n");
            // } else if( strcmp(recvbuf, "set_as_slave") == 0 ){
            } else if( strcmp(operator, "set_as_slave") == 0 ){
                printf("> received set_as slave\n");
                printf(">[fake-response] ....\n");
                // sleep(1);

                // strcpy(sendbuf, "set_as_slave_ok");
                strcpy(sendbuf, return_msg);
                send(new_fd, sendbuf, strlen(sendbuf), 0);
                printf("> send set_as_slave_ok\n");
            // } else if( strcmp(recvbuf, "set_host_dir") == 0 ){
            } else if( strcmp(operator, "set_host_dir") == 0 ){
                printf("> prentending to mount dir: %s\n", operand);


                char cmd[1024];
                sprintf(cmd, "mount -t nfs -o tcp,nolock %s", operand);
                fprintf(stdout, "cmd: %s\n", cmd);
                system(cmd);
                strcpy(sendbuf, return_msg);
                send(new_fd, sendbuf, strlen(sendbuf), 0);
                printf("> mount host dir done\n");
            } else if( strcmp(operator, "set_img_index") == 0 ){
                operand_i = atoi(operand);
                img_idx = operand_i;
            } else {
                printf("***** unresolved command ******\n");
                printf("strcmp(operator, \"set_exp_t\") = %d\n", strcmp(operator, "set_exp_t"));
                break;
            }

            // send(new_fd, sendbuf, sizeof(sendbuf), 0);
            // printf("> send %s to client\n", sendbuf);
        }

        memset(recvbuf,0,sizeof(recvbuf));
        memset(operator, 0, sizeof(operator));
        memset(operand, 0, sizeof(operand));

        // printf("> strlen(recvbuf) = %d\n", strlen(recvbuf));

        // usleep(1000);
        // sleep(1);
    }

    close(pinfo->fd);
    pinfo->fd = -1;

    close(new_fd); //评论中指出多了一次close，所以这里不close了，留待main函数中统一close

    fprintf(stdout, "(%s) closing thread: %lu\n",__func__, (unsigned long)pthread_self());
    pthread_exit(0);

    return NULL;
}

void startExposure(void *arg) {
    
    pthread_t ctid = pthread_self();
    fprintf(stdout, "[THREAD: %lu] exposure start ...\n", ctid);
    
    global_abort_exposure = 0;

    fprintf(stdout, "[%s] start exposure for %fs ...\n", __func__, exp_t_s);

    int t_cnt = 0;
    while( t_cnt < exp_t_s ){
    // while( t_cnt < 2 ){
        t_cnt ++;
        // fprintf(stdout, "t = %d\n", t_cnt);
        sleep(1);
        
        if( global_abort_exposure == 1 )
            break;
    }

    // if( global_abort_exposure == 0 ){
    //     fprintf(stdout, "[THREAD: %lu] exposure finish ...\n", ctid);
    // } else {
    //     fprintf(stdout, "[THREAD: %lu] exposure aborted ...\n", ctid);
    // }

    // return NULL;
}


void abortExposure(void *arg){

    // pthread_t ctid = pthread_self();
    global_abort_exposure = 1;
    // fprintf(stdout, "[THREAD: %lu] send 'ABORT EXPOSURE' signal!\n", ctid);
}



int extractCommand( const char sock_msg[1024], char *operator, char *operand ){
    char local_str[1024];
    strcpy(local_str, sock_msg);
    
    char *p;
    sprintf(operator, "%s", strtok(local_str, "|"));
    // fprintf(stdout, "##> operator: --%s--\n", operator);
    
    p = strtok(NULL, "|");

    if( p != NULL ){
        sprintf(operand, "%s\n", p);
        // fprintf(stdout, "##> operand:  --%s--\n", operand);
    }

    return 0;
}
